package com.googlecode.connectlet.crypto;

import java.lang.reflect.Field;

import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.KeyAgreement;
import javax.crypto.KeyAgreementSpi;
import javax.crypto.KeyGenerator;
import javax.crypto.KeyGeneratorSpi;
import javax.crypto.Mac;
import javax.crypto.MacSpi;
import javax.crypto.NullCipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKeyFactorySpi;

public class ProviderFactory {
	private Class<?> provider;
	private Class<?> spiClass;
	private String algorithm;

	private ProviderFactory(Class<?> provider, Class<?> spiClass, String algorithm) {
		this.provider = provider;
		this.spiClass = spiClass;
		this.algorithm = algorithm;
	}

	private Object generate(Object spi) {
		try {
			Object result = provider.getMethod("getInstance", String.class).
					invoke(null, algorithm);
			for (Field field : provider.getDeclaredFields()) {
				if (field.getType() == spiClass) {
					field.setAccessible(true);
					if (field.get(result) != null) {
						field.set(result, spi);
					}
				}
			}
			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private static ProviderFactory keyAgreementInstance =
			new ProviderFactory(KeyAgreement.class, KeyAgreementSpi.class, "DH");
	private static ProviderFactory keyGeneratorInstance =
			new ProviderFactory(KeyGenerator.class, KeyGeneratorSpi.class, "DES");
	private static ProviderFactory macInstance =
			new ProviderFactory(Mac.class, MacSpi.class, "HmacMD5");
	private static ProviderFactory secretKeyFactoryInstance =
			new ProviderFactory(SecretKeyFactory.class, SecretKeyFactorySpi.class, "DES");

	public static Cipher generateCipher(CipherSpi spi) {
		try {
			Cipher cipher = new NullCipher();
			for (Field field : Cipher.class.getDeclaredFields()) {
				if (field.getType() == CipherSpi.class) {
					field.setAccessible(true);
					if (field.get(cipher) != null) {
						field.set(cipher, spi);
					}
				}
			}
			return cipher;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static KeyAgreement generateKeyAgreement(KeyAgreementSpi spi) {
		return (KeyAgreement) keyAgreementInstance.generate(spi);
	}

	public static KeyGenerator generateKeyGenerator(KeyGeneratorSpi spi) {
		return (KeyGenerator) keyGeneratorInstance.generate(spi);
	}

	public static Mac generateMac(MacSpi spi) {
		return (Mac) macInstance.generate(spi);
	}

	public static SecretKeyFactory generateSecretKeyFactory(SecretKeyFactorySpi spi) {
		return (SecretKeyFactory) secretKeyFactoryInstance.generate(spi);
	}
}